using System;
using System.ComponentModel;
using System.Windows.Forms;
using CSW.Framework.Common;
using CSW.Framework.UI.Binding;
using DevExpress.XtraEditors;
using DevExpress.XtraEditors.Controls;

namespace CSW.Framework.UI.DevEx.Binding
{
    /// <summary>
    /// A wrapper class for binding <see cref="ComboBoxEdit"/> objects with <see cref="Binder"/>.
    /// </summary>
    public class BindableComboBoxEdit : IBindableControl
    {
        private readonly ComboBoxEdit m_ComboBoxEdit;
        private IBindingList m_ValueList;
        private bool m_IsDisposed;
        private object m_SelectedValue;

        /// <summary>
        /// Initializes a new instance of the <see cref="BindableComboBoxEdit"/> class.
        /// </summary>
        /// <param name="comboBoxEdit">The <see cref="ComboBoxEdit"/> control to wrap.</param>
        /// <param name="valueList">The value list.</param>
        /// <param name="addClearButton">if set to <c>true</c>, add a clear button.</param>
        public BindableComboBoxEdit(ComboBoxEdit comboBoxEdit, IBindingList valueList, bool addClearButton)
            : this(comboBoxEdit, addClearButton)
        {
            Guard.ArgumentNotNull(valueList, "valueList");

            ValueList = valueList;
        }

        private BindableComboBoxEdit(ComboBoxEdit comboBoxEdit, bool addClearButton)
        {
            Guard.ArgumentNotNull(comboBoxEdit, "comboBoxEdit");

            m_ComboBoxEdit = comboBoxEdit;
            m_ComboBoxEdit.Properties.Items.Clear();

            m_ComboBoxEdit.Validated += ComboBoxEdit_Validated;
            m_ComboBoxEdit.SelectedIndexChanged += ComboBoxEdit_Validated;
            m_ComboBoxEdit.Properties.TextEditStyle = TextEditStyles.DisableTextEditor;
            m_ComboBoxEdit.Properties.DropDownRows = 20;

            if (addClearButton)
            {
                // Search for butotn
                bool found = false;
                foreach (EditorButton button in m_ComboBoxEdit.Properties.Buttons)
                {
                    if (button.Kind == ButtonPredefines.Delete)
                    {
                        found = true;
                        break;
                    }
                }

                // If not found, add
                if (!found)
                    m_ComboBoxEdit.Properties.Buttons.Add(new EditorButton(ButtonPredefines.Delete));

                // Handle click
                m_ComboBoxEdit.ButtonClick += BaseEditEventHandlers.DeleteButtonClick;
                m_ComboBoxEdit.KeyDown += BaseEditEventHandlers.DeleteKeyDown;
            }
        }

        private void m_ValueList_ListChanged(object sender, ListChangedEventArgs e)
        {
            CheckDisposed();

            switch (e.ListChangedType)
            {
                case ListChangedType.Reset:
                    ResetComboBox();
                    break;

                case ListChangedType.ItemAdded:
                    m_ComboBoxEdit.Properties.Items.Add(m_ValueList[e.NewIndex]);
                    break;

                case ListChangedType.ItemChanged:
                case ListChangedType.ItemDeleted:
                    //e.NewIndex <-- something happened to this
                    ResetComboBox();
                    break;

                default:
                    throw new Exception(string.Format("ListChangedEventArgs.ListChangedType '{0}' not handled.", e.ListChangedType));
            }
        }

        /// <summary>
        /// Gets or sets the value list.
        /// </summary>
        /// <value>The value list.</value>
        protected IBindingList ValueList
        {
            get { return m_ValueList; }
            set
            {
                if (m_ValueList != null)
                    m_ValueList.ListChanged -= m_ValueList_ListChanged;

                m_ValueList = value;
                if (m_ValueList != null)
                    m_ValueList.ListChanged += m_ValueList_ListChanged;

                ResetComboBox();
            }
        }

        private void ResetComboBox()
        {
            m_ComboBoxEdit.Properties.Items.Clear();
            
            if (m_ValueList != null)
            {
                foreach (object item in m_ValueList)
                {
                    m_ComboBoxEdit.Properties.Items.Add(item);
                }
            }

            // This is to counter the following scenario:
            // - BindableObject contains a Property<BindingList<>>.
            // - The BindableObject responds to a list swap and resets the Property
            //   backing this ComboBoxEdit's SelectedValue.
            // - BindableComboBoxEdit then gets the event that the list changed.
            // - The SelectedValue was not set when the Property changed because the
            //   item was not in the list. Now that we have the updated list, it is,
            //   so try to reset it.
            object selectedValue = m_SelectedValue;
            if (selectedValue != null)
            {
                m_SelectedValue = null;
                Value = selectedValue;
            }
        }

        /// <summary>
        /// Checks if the object is disposed. If the object is disposed, an <see cref="ObjectDisposedException" /> is thrown.
        /// </summary>
        protected void CheckDisposed()
        {
            if (m_IsDisposed)
                throw new ObjectDisposedException(Name, string.Format("'{0}' is disposed.", Name));
        }

        private void ComboBoxEdit_Validated(object sender, EventArgs e)
        {
            CheckDisposed();

            EventHandler validated = Validated;
            if (validated != null)
            {
                validated(sender, e);
            }
        }

        #region IBindableControl Members

        /// <summary>
        /// Occurs when the control has validated.
        /// </summary>
        public event EventHandler Validated;

        /// <summary>
        /// Sends the <see cref="Validated"/> event.
        /// </summary>
        public void SendValidated()
        {
            EventHandler validated = Validated;
            if (validated != null)
                validated(this, EventArgs.Empty);
        }

        /// <summary>
        /// Gets the control this object wraps.
        /// </summary>
        /// <value>The control this object wraps.</value>
        public Control Control
        {
            get { return m_ComboBoxEdit; }
        }

        /// <summary>
        /// Gets or sets the value of the control.
        /// </summary>
        /// <value>The value of the control.</value>
        public object Value
        {
            get { return m_ComboBoxEdit.SelectedItem; }
            set
            {
                if (m_ComboBoxEdit.SelectedItem != value)
                {
                    m_ComboBoxEdit.SelectedItem = value;
                    if (m_ComboBoxEdit.SelectedItem != value)
                        m_SelectedValue = value;
                    else
                        m_SelectedValue = null;
                }
            }
        }

        /// <summary>
        /// Gets the name of the control.
        /// </summary>
        /// <value>The name of the control.</value>
        public string Name
        {
            get { return m_ComboBoxEdit.Name; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the control is enabled.
        /// </summary>
        /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
        public bool Enabled
        {
            get { return m_ComboBoxEdit.Enabled; }
            set { m_ComboBoxEdit.Enabled = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the control is read only.
        /// </summary>
        /// <value><c>true</c> if read only; otherwise, <c>false</c>.</value>
        public bool ReadOnly
        {
            get { return m_ComboBoxEdit.Properties.ReadOnly; }
            set 
            { 
                m_ComboBoxEdit.Properties.ReadOnly = value;
                m_ComboBoxEdit.TabStop = !value;

                foreach (EditorButton button in m_ComboBoxEdit.Properties.Buttons)
                {
                    button.Enabled = !value;
                }
            }
        }

        /// <summary>
        /// Focuses the control.
        /// </summary>
        public void Focus()
        {
            m_ComboBoxEdit.Focus();
        }

        /// <summary>
        /// Gets the tab index.
        /// </summary>
        /// <value>The tab index.</value>
        public int TabIndex
        {
            get { return m_ComboBoxEdit.TabIndex; }
        }

        /// <summary>
        /// Gets or sets the maximum string length.
        /// </summary>
        /// <value>The maximum string length.</value>
        public int MaxLength
        {
            get { throw new NotSupportedException("MaxLength is not supported."); }
            set { throw new NotSupportedException("MaxLength is not supported."); }
        }

        #endregion

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public virtual void Dispose()
        {
            Validated = null;
            m_ComboBoxEdit.Validated -= ComboBoxEdit_Validated;
            m_ComboBoxEdit.SelectedIndexChanged -= ComboBoxEdit_Validated;
            m_ComboBoxEdit.ButtonClick -= BaseEditEventHandlers.DeleteButtonClick;
            m_ComboBoxEdit.KeyDown -= BaseEditEventHandlers.DeleteKeyDown;

            if (m_ValueList != null)
                m_ValueList.ListChanged -= m_ValueList_ListChanged;

            m_IsDisposed = true;
        }
    }
}